package com.bzframework.pro.authorize.strategy.login;

import com.bzframework.pro.authorize.constants.RedisConstants;
import com.bzframework.pro.authorize.entity.UserEntity;
import com.bzframework.pro.authorize.service.UserService;
import com.bzframework.pro.authorize.token.SimpleUserToken;
import com.bzframework.pro.authorize.token.UserToken;
import com.bzframework.pro.authorize.token.generator.PasswordGenerator;
import com.bzframework.pro.notify.email.constants.MailConstants;
import lombok.RequiredArgsConstructor;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author bingzhong
 * @Date 2024/12/13 15:23
 * @Description
 */
@LoginStrategyConfig(strategy = LoginTypeEnum.email)
@RequiredArgsConstructor
public class EmailLoginStrategy implements LoginStrategy {

    private final ReactiveRedisOperations<String, String> redisOperations;

    private final PasswordGenerator passwordGenerator;

    private final UserService userService;

    private static final Duration tokenExpireTime = Duration.ofHours(2);

    @Override
    public Mono<UserToken> signIn(Map<String, Object> parameter) {

        String username = String.valueOf(parameter.get("username"));
        String token = IDGenerator.SNOW_FLAKE_STRING.generate();

        return Mono.zip(redisOperations.opsForValue()
                                       .get(MailConstants.getRedisKey(MailConstants.MAIL_LOGIN_CAPTCHA, username))
                                       .switchIfEmpty(Mono.error(new BusinessException("验证码已过期，请重新发送"))),
                        userService.createQuery()
                                   .where(UserEntity::getUsername, username)
                                   .fetchOne()
                                   .switchIfEmpty(Mono.defer(() -> createNewUser(username))))
                   .flatMap(tuple2 -> {
                       String generateCaptcha = tuple2.getT1();
                       String captcha = String.valueOf(parameter.get("captcha"));
                       if (!generateCaptcha.equalsIgnoreCase(captcha)) {
                           return Mono.error(new BusinessException("验证码错误"));
                       }
                       UserEntity userEntity = tuple2.getT2();
                       String role = userEntity.getRole().name();
                       Map<String, Object> tokenMap = new HashMap<>();
                       tokenMap.put("userId", userEntity.getId());
                       tokenMap.put("token", token);
                       String signInTime = String.valueOf(System.currentTimeMillis());
                       tokenMap.put("signInTime", signInTime);
                       tokenMap.put("role", role);
                       String tokenKey = RedisConstants.getRedisKey(RedisConstants.USER_TOKEN, token);
                       return redisOperations.opsForHash()
                                             .putAll(tokenKey, tokenMap)
                                             .then(redisOperations.expire(tokenKey, tokenExpireTime))
                                             .thenReturn(SimpleUserToken.of(userEntity.getId(), token, signInTime, role));
                   });
    }

    @Override
    public Mono<Long> signOut(String token) {
        return redisOperations.delete(RedisConstants.getRedisKey(RedisConstants.USER_TOKEN, token));
    }

    /**
     * 创建新的用户
     *
     * @param username
     * @return
     */
    private Mono<UserEntity> createNewUser(String username) {

        UserEntity userEntity = new UserEntity();
        userEntity.setName(username);
        userEntity.setUsername(username);
        String salt = IDGenerator.RANDOM.generate();
        String password = passwordGenerator.generateEncryptedPassword(salt, username);
        userEntity.setPassword(password);
        userEntity.setEmail(username);
        userEntity.setSalt(salt);
        userEntity.setCreateTime(System.currentTimeMillis());
        userEntity.setModifyTime(System.currentTimeMillis());
        return userService.save(userEntity)
                          .thenReturn(userEntity);
    }
}
